<?php
/**
 * This file contains the code to parse faces-config.xml
 * 
 * @license http://www.gnu.org/licenses/lgpl.html LGPL
 * @author Harlan Iverson <h dot iverson at gmail dot com>
 * @copyright Copyright (c) 2006, Harlan Iverson.
 * @version $Id: FacesConfig.php 20 2006-08-01 05:54:40Z Harlan $
 */
 

/**
 * A navigation rule entry
 */
class PhpFaces_Config_NavigationRuleConfigEntry {
	public $fromViewId;
	public $toViewId;
	public $fromAction;	
	public $fromOutcome;
	
	public function __construct( $fromViewId = null, $fromAction = null, $fromOutcome = null, $toViewId = null ) {
		$this->fromViewId = $fromViewId;
		$this->toViewId = $toViewId;
		$this->fromAction = $fromAction;	
		$this->fromOutcome = $fromOutcome;
	}
}

/**
 * A managed bean entry
 */
class PhpFaces_Config_ManagedBeanConfigEntry {
	public $name;
	public $class;
	public $scope;
}


class PhpFaces_Config_ComponentEntry {
	public $type;
	public $class;
}

class PhpFaces_Config_RenderKitEntry {
	public $id;
	public $class;
	public $renderers;
}

class PhpFaces_Config_RendererEntry {
	public $type;
	public $class;
	public $componentFamily;
	public $componentType;
}



/**
 * This is currently only a convenience class, however
 * I would like to migrate the entire config reader
 * to it... readManagedBean, readNavigationRule, etc
 */
class PhpFaces_Config_XMLReader extends XMLReader {
	
  /**
   * This function will return a string for the entire subtree, including
   * text of child nodes
   */
  function readString() {
       $depth = 1;
       $text = "";

       while ($this->read() && $depth != 0) {
           if (in_array($this->nodeType, array(XMLReader::TEXT, XMLReader::CDATA, XMLReader::WHITESPACE, XMLReader::SIGNIFICANT_WHITESPACE)))
               $text .= $this->value;
           if ($this->nodeType == XMLReader::ELEMENT) $depth++;
           if ($this->nodeType == XMLReader::END_ELEMENT) $depth--;
       }
       return $text;
   }
}

/**
 * The faces-config.xml parser class. It is given a file
 * path and makes accessor properties available.
 * 
 * It takes a regular format faces-config.xml as is used by JSF,
 * but also parses an additional element: phpfaces:require. Since
 * PHP doesn't have a "class path," this element simply lets you 
 * require files before the various beans or view/navigation handlers
 * are instantiated.
 */
class PhpFaces_Config_FacesConfig {
	/** @deprecated */
	public $path;
	
	public $requireFiles;
	public $managedBeans;
	public $navigationRules;
	
	public $navigationHandlers;
	public $viewHandlers;
	public $phaseListeners;
	
	private $renderKitEntries;
	private $componentEntries;

	private $logger;
	
	/**
	 * Constructor
	 * @param $path string the path to the config file
	 */
	public function __construct( $path = null ) {
		// backwards compat
		$this->path = $path;
		
		$this->requireFiles = array();
		$this->managedBeans = array();
		$this->navigationRules = array();
		$this->navigationHandlers = array();
		$this->viewHandlers = array();
		$this->phaseListeners = array();
		$this->componentEntries = array();
		$this->renderKitEntries = array();
 		
		$this->logger = LoggerManager::GetLogger( "PhpFaces.FacesConfig" );
		
		if( $path != null ) {
			$this->logger->warn( "You are creating FacesConfig with a deprecated constructor. Please update your code to pass the config file(s) into the parse method." );
		}
		
		
		
	}
	
	/**
	 * Parse the file given in the constructor
	 */
	public function parse( $path = null ) {
		
		// backwards compat
		if( $path == null ) {
			$this->logger->warn( "You are calling FacesConfig::parse with a deprecated signiture. Please update your code to pass the config file(s) into the parse method." );
			$path = $this->path;
		}
		$this->logger->debug( "Parsing ".$path );
		
		if( !$path ) {
			throw new Exception( "Empty config file path supplied" );
		}
		if( !file_exists($path) ) {
			throw new Exception( "Supplied config file does not exist: $path" );
		}


		$configXml = new PhpFaces_Config_XMLReader();
		$openSuccess = $configXml->open( $path );
		//$configXml->setParserProperty(2,true); // This seems a little unclear to me - but it worked :)
		 
		if( !$openSuccess ) {
			throw new Exception( "Could not open config file $path" );
		}
		
		$isInConfig = false;
		$isInLifecycle = false;
		$componentEntry = null;
		$renderKitEntry = null;
		$isInApplication = false;
		
		$managedBean = null;
		$textBuffer = "";
				
		while( $configXml->read() ) {
			
			//$this->logger->debug( "Element: ".$configXml->namespaceURI." - ".$configXml->localName  );
			switch( $configXml->namespaceURI ) {
				
				// ====================================================================
				//		Faces Config Elements
				// ====================================================================
				
				case "":
				case "http://java.sun.com/JSF/Configuration": // WTF? depending on which machine, it has been either. possibly to do with validation?
					switch( $configXml->localName ) {
						
						// ====================================================================
						//		Application Elements
						// ====================================================================
						
						case "application":
							if( $configXml->nodeType == XMLReader::ELEMENT ) {
								$isInApplication = true;
							} else if( $configXml->nodeType == XMLReader::END_ELEMENT ) {
								$isInApplication = false;
							}
							break;
						case "navigation-handler":
							if( $configXml->nodeType == XMLReader::ELEMENT ) {
								$navigationHandler = trim( $configXml->readString() );
								$this->navigationHandlers[] = $navigationHandler;
							} 
							break;
						case "view-handler":
							if( $configXml->nodeType == XMLReader::ELEMENT ) {
								$viewHandler  = trim( $configXml->readString() );
								$this->viewHandlers[] = $viewHandler;
							}
							break;
							
							
						// ====================================================================
						//		Component Tags
						//
						// @todo do we want to create a sub loop and handle all elements within?
						// ====================================================================
						
						
						case "component": 
							if( $configXml->nodeType == XMLReader::ELEMENT ) {
								$componentEntry = new PhpFaces_Config_ComponentEntry();
							} else if( $configXml->nodeType == XMLReader::END_ELEMENT ) {
								$this->componentEntries[] = $componentEntry;
								$componentEntry = null;
							}
							break;
							
						case "component-type":
							if( $componentEntry != null ) {
								if( $configXml->nodeType == XMLReader::ELEMENT ) {
									$componentType = trim( $configXml->readString() );
									$componentEntry->type = $componentType;
								}
							}
							break;
						case "component-class":
							if( $componentEntry != null ) {
								if( $configXml->nodeType == XMLReader::ELEMENT ) {
									$componentClass = trim( $configXml->readString() );
									if( $componentClass ) {
										$componentEntry->class = $componentClass;
									}
								}
							}
							break;
							
							
						// ====================================================================
						//		Render Kit Tags
						// ====================================================================
						
						
						case "render-kit": 
							if( $configXml->nodeType == XMLReader::ELEMENT ) {
								$renderKitEntry = new PhpFaces_Config_RenderKitEntry();
							} else if( $configXml->nodeType == XMLReader::END_ELEMENT ) {
								$this->renderKitEntries[] = $renderKitEntry;
								$renderKitEntry = null;
							}
							break;
							
						case "render-kit-id": // assumes default renderkit ID
							if( $renderKitEntry != null ) {
								if( $configXml->nodeType == XMLReader::ELEMENT ) {
									$id = trim( $configXml->readString() );
									if( $id ) {
										$renderKitEntry->id = $id;
									}
								}
							}
							break;
						case "render-kit-class":
							if( $renderKitEntry != null ) {
								if( $configXml->nodeType == XMLReader::ELEMENT ) {
									$class = trim( $configXml->readString() );
									if( $class ) {
										$renderKitEntry->class = $class;
									}
								}
							}
							break;
						// ====================================================================
						//		Renderer Elements
						// ====================================================================
						case "renderer":
							if( $renderKitEntry != null ) {
								if( $configXml->nodeType == XMLReader::ELEMENT ) {
									$rendererEntry = new PhpFaces_Config_RendererEntry();
								} else if( $configXml->nodeType == XMLReader::END_ELEMENT ) {
									$renderKitEntry->renderers[] = $rendererEntry;
									$rendererEntry  = null;
								}
							}
							break;
						
						// TODO what is renderer-type used for?
						case "renderer-type":
							if( $rendererEntry != null ) {
								if( $configXml->nodeType == XMLReader::ELEMENT ) {
									$rendererEntry->type = trim( $configXml->readString() );
								}
							}
							break;
							
						case "renderer-class":
							if( $rendererEntry != null ) {
								if( $configXml->nodeType == XMLReader::ELEMENT ) {
									$rendererEntry->class = trim( $configXml->readString() );
								}
							}
							break;
							
						case "component-family":
							if( $rendererEntry != null ) {
								if( $configXml->nodeType == XMLReader::ELEMENT ) {
									$rendererEntry->componentFamily = trim( $configXml->readString() );
								}
							}
							break;
							
						case "supported-component-type":
							if( $rendererEntry != null ) {
								if( $configXml->nodeType == XMLReader::ELEMENT ) {
									$rendererEntry->componentType = trim( $configXml->readString() );
								}
							}
							break;
							
							
							
						// ====================================================================
						//		Lifecycle Elements
						// ====================================================================
						
						
						case "lifecycle":
							if( $configXml->nodeType == XMLReader::ELEMENT ) {
								$isInLifecycle = true;
							} else if( $configXml->nodeType == XMLReader::END_ELEMENT ) {
								$isInLifecycle = false;
							}
							break;
							
						case "phase-listener":
							if( $isInLifecycle ) {
								if( $configXml->nodeType == XMLReader::ELEMENT ) {
									$phaseListenerClassName = trim( $configXml->readString() );
									if( $phaseListenerClassName ) {
										$this->phaseListeners[] = $phaseListenerClassName;
									}
								}
							}
							break;
							
						// ====================================================================
						//		Managed Bean Elements
						// ====================================================================
							
						case "managed-bean":
							if( $configXml->nodeType == XMLReader::ELEMENT ) {
								$managedBean = new PhpFaces_Config_ManagedBeanConfigEntry();
							} else if( $configXml->nodeType == XMLReader::END_ELEMENT ) {
								$this->managedBeans[] = $managedBean;
								$managedBean = null;
							}
							break;
							
						case "managed-bean-name":
							if( $managedBean != null ) {
								if( $configXml->nodeType == XMLReader::ELEMENT ) {
									$managedBean->name = trim( $configXml->readString() );
								}
							}
							break;
						case "managed-bean-class":
							if( $managedBean != null ) {
								if( $configXml->nodeType == XMLReader::ELEMENT ) {
									$managedBean->class = trim( $configXml->readString() );
								}
							}
							break;
						case "managed-bean-scope":
							if( $managedBean != null ) {
								if( $configXml->nodeType == XMLReader::ELEMENT ) {
									$managedBean->scope = trim( $configXml->readString() );
								}
							}
							break;
							
						// ====================================================================
						//		Navigation Rule Elements
						// ====================================================================
							
						case "navigation-rule":
							if( $configXml->nodeType == XMLReader::ELEMENT ) {
								$navRule = new PhpFaces_Config_NavigationRuleConfigEntry();
							} else if ( $configXml->nodeType == XMLReader::END_ELEMENT ) {
								$this->navigationRules[] = $navRule;
								$navRule = null;
							}
							break;
						// handle all cases the same, but put them in different places
						case "from-view-id":
							if( $navRule != null ) {
								if( $configXml->nodeType == XMLReader::ELEMENT ) {
									$navRule->fromViewId = trim( $configXml->readString() );
								}
							}
							break;
						case "to-view-id":
							if( $navRule != null ) {
								if( $configXml->nodeType == XMLReader::ELEMENT ) {
									$navRule->toViewId = trim( $configXml->readString() );
								}
							}
							break;
						case "from-action":
							if( $navRule != null ) {
								if( $configXml->nodeType == XMLReader::ELEMENT ) {
									$navRule->fromAction = trim( $configXml->readString() );;
								}
							}
							break;
						case "from-outcome":
							if( $navRule != null ) {
								if( $configXml->nodeType == XMLReader::ELEMENT ) {
									$navRule->fromOutcome = trim( $configXml->readString() );
								}
							}
							break;	
					}
					break;
			
				// ====================================================================
				//		PhpFaces Specific Elements
				// ====================================================================
			
				case "urn:php-faces":
					switch( $configXml->localName ) {
						case "require":
							if( $isInApplication && ($configXml->nodeType == XMLReader::ELEMENT || $configXml->isEmptyElement) ) {
								$this->requireFiles[] = $configXml->getAttribute( "file" );
							} 
							break;
					}
			}
			
		}

	}
	
	
	/**
	 * Get an array of required files (phpfaces:require)
	 * @return string[] a string array of paths
	 */
	public function getRequiredFiles() {
		return $this->requireFiles;
	}
	
	/**
	 * Returns an array of managed beans (managed-bean)
	 * @return PhpFaces_Config_ManagedBeanConfigEntry[] an array of managed config bean entries 
	 */
	public function getManagedBeans() {
		return $this->managedBeans;
	}
	
	/**
	 * Returns an array of navigation rules (navigation-rule
	 * @return PhpFaces_Config_NavigationRuleConfigEntry[] an array of navigation rule entries 
	 */
	public function getNavigationRules() {
		return $this->navigationRules;
	}
	
	/**
	 * Returns an array of navigation handlers
	 * @return array an array of class names to decorate, in order
	 */
	public function getNavigationHandlers() {
		return $this->navigationHandlers;
	}
	
	/**
	 * Returns an array of view handlers
	 * @return array an array of class names to decorate, in order
	 */
	public function getViewHandlers() {
		return $this->viewHandlers;
	}
	
	public function getPhaseListeners() {
		return $this->phaseListeners;
	}
	
	public function getComponentEntries() {
		return $this->componentEntries;
	}
	
	public function getRenderKitEntries() {
		return $this->renderKitEntries;	
	}
}

class PhpFaces_Taglib_Tag {
	public $name;
	public $componentType;
	
	public function getName() {
		return $this->name();
	}
	public function getComponentType() {
		return $this->componentType;
	}
}

class PhpFaces_Taglib {
	private $path;
	
	private $namespace;
	private $tags;
	
	public function __construct( $path ) {
		$this->path = $path;
	}
	
	public function parse() {
		$configXml = new XmlReader();
		$configXml->open( $this->path );
		$configXml->setParserProperty(2,true); // This seems a little unclear to me - but it worked :)
		
		$textBuffer = null;
		$tag = null;
		
		while( $configXml->read() ) {
			
			if( $configXml->nodeType == XMLReader::TEXT
				// || $configXml->nodeType == XMLReader::SIGNIFICANT_WHITESPACE
				|| $configXml->nodeType == XMLReader::CDATA ) {
					
				$textBuffer .= $configXml->value;	
				continue;		
			}
			
			switch( $configXml->namespaceURI ) {
				case "":
					switch( $configXml->localName ) {
						case "facelet-taglib":
							break;
						case "namespace":
							break;
						case "tag":
							if( $configXml->nodeType == XMLReader::ELEMENT ) {
								$tag = new PhpFaces_Taglib_Tag();
							} else if ( $configXml->nodeType == XMLReader::END_ELEMENT ) {
								$this->tags[] = $tag;
								$tag = null;
							}
							break;
						case "tag-name": 
							if( $tag != null ) {
								if( $configXml->nodeType == XMLReader::ELEMENT ) {
									$textBuffer = "";
								} else if ( $configXml->nodeType == XMLReader::END_ELEMENT ) {
									$tag->name = trim( $textBuffer );
									$textBuffer = null;
								}
							}
							break;
						case "component":
							
							break;
						case "component-type":
							if( $tag != null ) {
								if( $configXml->nodeType == XMLReader::ELEMENT ) {
									$textBuffer = "";
								} else if ( $configXml->nodeType == XMLReader::END_ELEMENT ) {
									$tag->componentType = trim( $textBuffer );
									$textBuffer = null;
								}
							}
							break;
						case "renderer-type":
							break;
					}
					break;
				case "urn:php-faces":
					switch( $configXml->localName ) {
						case "require":

							break;
					}
			}
			
		}
	}
	
	public function getNamespace() {
		return $this->namespace;
	}
	
	public function getTagEntries() {
		return $this->tags;
	}
	
	
	public function lookupTag( $tagName ) {
		foreach( $this->getTags() as $tag ) {
			if( $tag->getName() == $tagName ) {
				return $tag;
			}
		}
	}
	
}

/*
$config = new FacesConfig( "../../faces-config.xml" );
$config->parse();

print_r( $config );
*/
?>
